package com.aplink.generic.google.maps;

import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import android.view.View;

import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;

/**
 * Keeps track of collections of markers on the map. Delegates all
 * Marker-related events to each collection's individually managed listeners.
 * <p/>
 * All marker operations (adds and removes) should occur via its collection
 * class. That is, don't add a marker via a collection, then remove it via
 * Marker.remove()
 */
public class MarkerManager implements GoogleMap.OnInfoWindowClickListener,
        GoogleMap.OnMarkerClickListener, GoogleMap.OnMarkerDragListener,
        GoogleMap.InfoWindowAdapter {
	public class Collection {
		private GoogleMap.InfoWindowAdapter mInfoWindowAdapter;
		private GoogleMap.OnInfoWindowClickListener mInfoWindowClickListener;
		private GoogleMap.OnMarkerClickListener mMarkerClickListener;
		private GoogleMap.OnMarkerDragListener mMarkerDragListener;
		private final Set<Marker> mMarkers = new HashSet<Marker>();

		public Collection() {
		}

		public Marker addMarker(final MarkerOptions opts) {
			final Marker marker = mMap.addMarker(opts);
			mMarkers.add(marker);
			mAllMarkers.put(marker, Collection.this);
			return marker;
		}

		public void clear() {
			for (final Marker marker : mMarkers) {
				marker.remove();
				mAllMarkers.remove(marker);
			}
			mMarkers.clear();
		}

		public java.util.Collection<Marker> getMarkers() {
			return Collections.unmodifiableCollection(mMarkers);
		}

		public boolean remove(final Marker marker) {
			if (mMarkers.remove(marker)) {
				mAllMarkers.remove(marker);
				marker.remove();
				return true;
			}
			return false;
		}

		public void setOnInfoWindowAdapter(
		        final GoogleMap.InfoWindowAdapter infoWindowAdapter) {
			mInfoWindowAdapter = infoWindowAdapter;
		}

		public void setOnInfoWindowClickListener(
		        final GoogleMap.OnInfoWindowClickListener infoWindowClickListener) {
			mInfoWindowClickListener = infoWindowClickListener;
		}

		public void setOnMarkerClickListener(
		        final GoogleMap.OnMarkerClickListener markerClickListener) {
			mMarkerClickListener = markerClickListener;
		}

		public void setOnMarkerDragListener(
		        final GoogleMap.OnMarkerDragListener markerDragListener) {
			mMarkerDragListener = markerDragListener;
		}
	}

	private final Map<Marker, Collection> mAllMarkers = new HashMap<Marker, Collection>();
	private final GoogleMap mMap;

	private final Map<String, Collection> mNamedCollections = new HashMap<String, Collection>();

	public MarkerManager(final GoogleMap map) {
		this.mMap = map;
	}

	/**
	 * Gets a named collection that was created by
	 * {@link #newCollection(String)}
	 * 
	 * @param id
	 *            the unique id for this collection.
	 */
	public Collection getCollection(final String id) {
		return mNamedCollections.get(id);
	}

	@Override
	public View getInfoContents(final Marker marker) {
		final Collection collection = mAllMarkers.get(marker);
		if ((collection != null) && (collection.mInfoWindowAdapter != null)) {
			return collection.mInfoWindowAdapter.getInfoContents(marker);
		}
		return null;
	}

	@Override
	public View getInfoWindow(final Marker marker) {
		final Collection collection = mAllMarkers.get(marker);
		if ((collection != null) && (collection.mInfoWindowAdapter != null)) {
			return collection.mInfoWindowAdapter.getInfoWindow(marker);
		}
		return null;
	}

	public Collection newCollection() {
		return new Collection();
	}

	/**
	 * Create a new named collection, which can later be looked up by
	 * {@link #getCollection(String)}
	 * 
	 * @param id
	 *            a unique id for this collection.
	 */
	public Collection newCollection(final String id) {
		if (mNamedCollections.get(id) != null) {
			throw new IllegalArgumentException("collection id is not unique: "
			        + id);
		}
		final Collection collection = new Collection();
		mNamedCollections.put(id, collection);
		return collection;
	}

	@Override
	public void onInfoWindowClick(final Marker marker) {
		final Collection collection = mAllMarkers.get(marker);
		if ((collection != null)
		        && (collection.mInfoWindowClickListener != null)) {
			collection.mInfoWindowClickListener.onInfoWindowClick(marker);
		}
	}

	@Override
	public boolean onMarkerClick(final Marker marker) {
		final Collection collection = mAllMarkers.get(marker);
		if ((collection != null) && (collection.mMarkerClickListener != null)) {
			return collection.mMarkerClickListener.onMarkerClick(marker);
		}
		return false;
	}

	@Override
	public void onMarkerDrag(final Marker marker) {
		final Collection collection = mAllMarkers.get(marker);
		if ((collection != null) && (collection.mMarkerDragListener != null)) {
			collection.mMarkerDragListener.onMarkerDrag(marker);
		}
	}

	@Override
	public void onMarkerDragEnd(final Marker marker) {
		final Collection collection = mAllMarkers.get(marker);
		if ((collection != null) && (collection.mMarkerDragListener != null)) {
			collection.mMarkerDragListener.onMarkerDragEnd(marker);
		}
	}

	@Override
	public void onMarkerDragStart(final Marker marker) {
		final Collection collection = mAllMarkers.get(marker);
		if ((collection != null) && (collection.mMarkerDragListener != null)) {
			collection.mMarkerDragListener.onMarkerDragStart(marker);
		}
	}

	/**
	 * Removes a marker from its collection.
	 * 
	 * @param marker
	 *            the marker to remove.
	 * @return true if the marker was removed.
	 */
	public boolean remove(final Marker marker) {
		final Collection collection = mAllMarkers.get(marker);
		return (collection != null) && collection.remove(marker);
	}
}
